﻿using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reactive;
using System.Reactive.Concurrency;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace SimpleActor.Test
{
    [TestClass]
    public class MessageSchedulerTest
    {
        [TestMethod]
        public void GetQueueActionTest()
        {
            TestServiceScope.Create(service =>
            {
                object parameter = null;

                parameter = service.GetParameter().Result;
                parameter.IsNull();

                service.Action0();
                parameter = service.GetParameter().Result;
                parameter.Is(Unit.Default);
                Debug.WriteLine("Result: {0} / {1}", parameter.GetType(), parameter);

                service.Action1(0);
                parameter = service.GetParameter().Result;
                parameter.Is(Tuple.Create<byte>(0));
                Debug.WriteLine("Result: {0} / {1}", parameter.GetType(), parameter);

                service.Action2(1, 2);
                parameter = service.GetParameter().Result;
                parameter.Is(Tuple.Create<byte, short>(1, 2));
                Debug.WriteLine("Result: {0} / {1}", parameter.GetType(), parameter);

                service.Action3(3, 4, 5);
                parameter = service.GetParameter().Result;
                parameter.Is(Tuple.Create<byte, short, int>(3, 4, 5));
                Debug.WriteLine("Result: {0} / {1}", parameter.GetType(), parameter);

                service.Action4(6, 7, 8, 9);
                parameter = service.GetParameter().Result;
                parameter.Is(Tuple.Create<byte, short, int, long>(6, 7, 8, 9));
                Debug.WriteLine("Result: {0} / {1}", parameter.GetType(), parameter);
            });
        }

        [TestMethod]
        public void GetGetQueueFuncTest()
        {
            TestServiceScope.Create(service =>
            {
                object parameter = null;
                string result = null;

                parameter = service.GetParameter().Result;
                parameter.IsNull();

                result = service.Func0().Result;
                parameter = service.GetParameter().Result;
                result.Is("Func0", result);
                parameter.Is(Unit.Default);
                Debug.WriteLine("Result: {0} / {1}", parameter.GetType(), parameter);

                result = service.Func1(9).Result;
                parameter = service.GetParameter().Result;
                result.Is("Func1");
                parameter.Is(Tuple.Create<long>(9));
                Debug.WriteLine("Result: {0} / {1}", parameter.GetType(), parameter);

                result = service.Func2(8, 7).Result;
                parameter = service.GetParameter().Result;
                result.Is("Func2");
                parameter.Is(Tuple.Create<long, int>(8, 7));
                Debug.WriteLine("Result: {0} / {1}", parameter.GetType(), parameter);

                result = service.Func3(6, 5, 4).Result;
                parameter = service.GetParameter().Result;
                result.Is("Func3");
                parameter.Is(Tuple.Create<long, int, short>(6, 5, 4));
                Debug.WriteLine("Result: {0} / {1}", parameter.GetType(), parameter);

                result = service.Func4(3, 2, 1, 0).Result;
                parameter = service.GetParameter().Result;
                result.Is("Func4");
                parameter.Is(Tuple.Create<long, int, short, byte>(3, 2, 1, 0));
                Debug.WriteLine("Result: {0} / {1}", parameter.GetType(), parameter);
            });
        }

        [TestMethod]
        public void TryHandleMessageTestException()
        {
            TestServiceScope.CreateDetail((proxy, scheduler, servant) =>
            {
                AssertEx.Throws<InvalidOperationException>(() =>scheduler.TryHandleMessage(Timeout.InfiniteTimeSpan));
            });
        }

        [TestMethod]
        public void DiscardQueuedMessagesTestA()
        {
            var delayA = TimeSpan.FromMilliseconds(100);
            var delayB = TimeSpan.FromMilliseconds(200);
            var threshold = TimeSpan.FromMilliseconds(50);

            var sw = Stopwatch.StartNew();

            Task taskA = null;
            Task taskB = null;

            try
            {
                TestServiceScope.Create(service =>
                {
                    taskA = service.LongAction0ThrowsException(delayA);
                    taskA.Status.Is(TaskStatus.WaitingForActivation);

                    var time0 = sw.Elapsed;
                    Debug.WriteLine(@"Time[0]: {0:ss\.fff}", time0);

                    taskB = service.Action1(0);

                    var time1 = sw.Elapsed;
                    Debug.WriteLine(@"Time[1]: {0:ss\.fff}", time1);

                    Thread.Sleep(delayB + threshold);

                    var time2 = sw.Elapsed;
                    Debug.WriteLine(@"Time[2]: {0:ss\.fff}", time2);

                    taskA.Status.Is(TaskStatus.Faulted);
                    taskA.Exception.InnerException.IsInstanceOf<SampleException>();

                    taskB.Status.Is(TaskStatus.Faulted);
                    taskB.Exception.InnerException.IsInstanceOf<InvalidOperationException>();

                    var time3 = sw.Elapsed;
                    Debug.WriteLine(@"Time[3]: {0:ss\.fff}", time3);
                });
            }
            catch (AggregateException ex)
            {
                var cause = ex.InnerException;
                cause.IsInstanceOf<SampleException>();
                Debug.WriteLine("{0}: {1}", cause.GetType().Name, cause.Message);
                Debug.WriteLine(cause.ToString());
            }

            taskA.IsNotNull();
            taskB.IsNotNull();

            var time4 = sw.Elapsed;
            (time4 > delayB + threshold).IsTrue();
        }

        [TestMethod]
        public void DiscardQueuedMessagesTestB()
        {
            var delayA = TimeSpan.FromMilliseconds(100);
            var delayB = TimeSpan.FromMilliseconds(200);
            var threshold = TimeSpan.FromMilliseconds(50);

            var sw = Stopwatch.StartNew();

            Task taskA = null;
            Task taskB = null;

            try
            {
                TestServiceScope.Create(service =>
                {
                    taskA = service.AsyncAction1(0, delayA);
                    taskA.Status.Is(TaskStatus.WaitingForActivation);

                    var time0 = sw.Elapsed;
                    Debug.WriteLine(@"Time[0]: {0:ss\.fff}", time0);

                    taskB = service.LongAction0ThrowsException(delayB);

                    var time1 = sw.Elapsed;
                    Debug.WriteLine(@"Time[1]: {0:ss\.fff}", time1);

                    Thread.Sleep(delayB + threshold);

                    var time2 = sw.Elapsed;
                    Debug.WriteLine(@"Time[2]: {0:ss\.fff}", time2);

                    taskA.Status.Is(TaskStatus.WaitingForActivation);

                    taskB.Status.Is(TaskStatus.Faulted);
                    taskB.Exception.InnerException.IsInstanceOf<SampleException>();

                    var time3 = sw.Elapsed;
                    Debug.WriteLine(@"Time[3]: {0:ss\.fff}", time3);
                });
            }
            catch (AggregateException ex)
            {
                var cause = ex.InnerException;
                cause.IsInstanceOf<SampleException>();
                Debug.WriteLine("{0}: {1}", cause.GetType().Name, cause.Message);
                Debug.WriteLine(cause.ToString());
            }

            taskA.IsNotNull();
            taskB.IsNotNull();

            var time4 = sw.Elapsed;
            (time4 > delayB + threshold).IsTrue();

            // TODO documantation (taskA is not completed)
            taskA.Status.Is(TaskStatus.WaitingForActivation);
        }

        [TestMethod]
        public void StartCoreTestException()
        {
            var servant = new TestServant(TestMode.ThrowOnStart);
            var scheduler = new MessageScheduler<ITestServant>("Service", servant);
            var proxy = new TestServiceProxy(scheduler);

            var start = proxy.Start();
            AssertEx.Throws<AggregateException>(() => start.Wait());

            var stop = proxy.Stop();
            AssertEx.Throws<AggregateException>(() => stop.Wait());

            start.Exception.InnerExceptions.Count.Is(1);
            start.Exception.InnerException.IsInstanceOf<SampleException>();
            Debug.WriteLine("Start:");
            Debug.Indent();
            Debug.WriteLine(start.Exception);
            Debug.Unindent();

            stop.Exception.InnerException.IsSameReferenceAs(start.Exception.InnerException);
        }

        [TestMethod]
        public void StopCoreTestExceptionA()
        {
            var servant = new TestServant(TestMode.ThrowOnStop);
            var scheduler = new MessageScheduler<ITestServant>("Service", servant);
            var proxy = new TestServiceProxy(scheduler);

            var start = proxy.Start();
            AssertEx.DoesNotThrow(() => start.Wait());

            var stop = proxy.Stop();
            AssertEx.Throws<AggregateException>(() => stop.Wait());

            stop.Exception.InnerExceptions.Count.Is(1);
            stop.Exception.InnerException.IsInstanceOf<SampleException>();
            Debug.WriteLine("Stop:");
            Debug.Indent();
            Debug.WriteLine(stop.Exception);
            Debug.Unindent();
        }

        [TestMethod]
        public void StopCoreTestExceptionB()
        {
            var servant = new TestServant(TestMode.ThrowOnStart | TestMode.ThrowOnStop);
            var scheduler = new MessageScheduler<ITestServant>("Service", servant);
            var proxy = new TestServiceProxy(scheduler);

            var start = proxy.Start();
            AssertEx.Throws<AggregateException>(() => start.Wait());

            var stop = proxy.Stop();
            AssertEx.Throws<AggregateException>(() => stop.Wait());

            start.Exception.InnerExceptions.Count.Is(1);
            start.Exception.InnerException.IsInstanceOf<SampleException>();
            Debug.WriteLine("Start:");
            Debug.Indent();
            Debug.WriteLine(start.Exception);
            Debug.Unindent();


            stop.Exception.IsInstanceOf<AggregateException>();
            stop.Exception.InnerExceptions.Count.Is(1);
            
            var ex = stop.Exception.Flatten();
            ex.InnerExceptions.Count.Is(2);
            Debug.WriteLine("Stop:");
            Debug.Indent();
            Debug.WriteLine(ex);
            Debug.Unindent();
        }
    }
}
